#[macro_use]
extern crate tera;
use cc;
use regex::Regex;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use std::process::Command;

fn main() -> Result<(), std::io::Error> {
    let mq_libs_path = PathBuf::from(
        fs::read_to_string(".mqlibpath")
            .expect("No .mqlibpath file found in workspace!")
            .trim(),
    );
    if !mq_libs_path.is_dir() {
        panic!("Path in .mqlibpath is not a directory!");
    }
    let bindings_temp_fname = "./src/mqcc.rs.temp";
    let bindings_fname = "./src/mqi.rs";
    // let constants_fname = "./src/mqcc.rs";
    // let _ = fs::remove_file(constants_fname);
    let _ = fs::remove_file(bindings_fname);
    let _ = fs::remove_file(bindings_temp_fname);
    match Command::new("bindgen")
        .args(&["--impl-debug", "-o", bindings_temp_fname, "wrapper.h"])
        .status()
    {
        Ok(_) => {
            println!("Bindings generated to temporary file");
            let ftemp = File::open(bindings_temp_fname)?;
            let mut ffinal = File::create(bindings_fname)?;
            let mqrc_re = Regex::new(r"^pub.const.(.+?)_(.*?):.*=.(-*\d*);").unwrap();
            writeln!(ffinal, r"#![allow(non_upper_case_globals)]")?;
            writeln!(ffinal, r"#![allow(non_camel_case_types)]")?;
            writeln!(ffinal, r"#![allow(non_snake_case)]")?;
            let mut constants: HashMap<String, HashMap<i32, String>> = HashMap::new();
            for (num, line) in BufReader::new(ftemp).lines().enumerate() {
                let line = line.unwrap();
                let line = if num < 2800
                    && line.contains("u32")
                    && !line.contains("MQRO_ACCEPT_UNSUP_MASK")
                    && !line.contains("MQENC_RESERVED_MASK")
                    && !line.contains("MQMF_ACCEPT_UNSUP_MASK")
                    && !line.contains("MQPD_REJECT_UNSUP_MASK")
                    && !line.contains("MQRFH_FLAGS_RESTRICTED_MASK")
                {
                    line.replace("u32", "i32")
                } else {
                    line
                };
                writeln!(ffinal, "{}", line)?;
                if mqrc_re.is_match(&line) {
                    let caps = mqrc_re.captures(&line).unwrap();
                    let group = caps.get(1).unwrap().as_str().to_owned();
                    let mut const_name = group.clone();
                    const_name.push_str("_");
                    const_name.push_str(caps.get(2).unwrap().as_str());
                    if group == "MQRC" || group == "MQCC" {
                        let map = constants.entry(group).or_insert(HashMap::new());
                        map.insert(
                            caps.get(3).unwrap().as_str().parse::<i32>().unwrap(),
                            const_name,
                        );
                    }
                }
            }
            generate_conversion_functions(
                &constants.get("MQCC").unwrap(),
                &constants.get("MQRC").unwrap(),
            )?;
            fs::remove_file(bindings_temp_fname)?;
        }
        Err(e) => {
            panic!("Error generating bindings {:?}", e);
        }
    }

    cc::Build::new()
        .compiler("gcc")
        .include("./include/")
        .flag("-nostartfiles")
        .flag("-m64")
        .static_flag(false)
        .flag("-lmqm_r")
        .file("src/c/glue.c")
        .warnings(true)
        .compile("glue");
    let _out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rustc-link-search=native={}", mq_libs_path.display());
    println!("cargo:rustc-link-lib=mqm_r");
    Ok(())
}

use tera::Context;
fn generate_conversion_functions(
    cc_int_code: &HashMap<i32, String>,
    rc_int_code: &HashMap<i32, String>,
) -> Result<(), std::io::Error> {
    let mut tera = compile_templates!("./templates/*.tera");
    tera.autoescape_on(vec![".tera"]);
    let mut context = Context::new();
    context.insert("cc_int_code", cc_int_code);
    context.insert("rc_int_code", rc_int_code);
    let result = tera.render("conversions.tera", &context).unwrap();
    let mut f = File::create("src/conversions.rs").unwrap();
    write!(f, "{}", result)?;
    Ok(())
}

#[allow(dead_code)]
fn generate_all(constants: &HashMap<String, HashMap<i32, String>>) -> Result<(), std::io::Error> {
    for (mq_name, map) in constants {
        let (rs_name, is_ok_val) = match mq_name.as_ref() {
            "MQCC" => ("ComplCode", Some("MQCC_OK")),
            "MQRC" => ("ReasonCode", Some("MQRC_NONE")),
            _ => (mq_name.as_ref(), None),
        };
        let mut f = File::create(&format!("./src/{}.rs", mq_name.to_lowercase())).unwrap();
        let module = generate_enum(rs_name, map, is_ok_val);
        write!(f, "{}", module)?;
    }
    Ok(())
}

#[allow(dead_code)]
fn generate_enum(name: &str, int_code: &HashMap<i32, String>, is_ok_val: Option<&str>) -> String {
    let mut tera = compile_templates!("./templates/*.tera");
    tera.autoescape_on(vec![".tera"]);
    let mut context = Context::new();
    context.insert("name", name);
    context.insert("int_code", int_code);
    if let Some(is_ok_val) = is_ok_val {
        context.insert("is_ok_val", is_ok_val);
    }
    tera.render("mqcc.tera", &context).unwrap()
}
